Function Write-Log(){
    [CmdletBinding()]
	param(
		[Parameter(Mandatory=$true)]
		[ValidateSet("Warning", "Error", "Success","Normal","Critical")]
		[string]$log_type,
		[string]$log_info,
		[string]$log_file,
        [Parameter(Mandatory=$false)]
        [switch]$print_to_console
	)
    If( $print_to_console ){
        $color = switch -regex ($log_type){
            "Error|Critical"  {'red';break }
            "Warning"{'yellow' ;break}
            "Success"{'green' ;break }
            default {'white'}
        }
        write-host -fore $color $log_info
    }
	Do{
	    Try{"{0:yyyy-MM-dd HH:mm:ss}--{1}: {2}" -f (get-date),$log_type,$log_info |Out-File -FilePath $log_file -Append -Encoding utf8 -Force -ea 1;break}Catch{sleep -Milliseconds 200 ;if(++$cnt -gt 10){write-error "wait too long(2s) to write log";break}
		}Finally{}
	}While(1)
}
Function New-FileOrFolder($p,[Switch]$d,$log_file){
  if(!(test-path $p -ea 0)){
    Try{
        $ItemType=Switch ($d){ $true{'Directory'}  $false{'File'} }
        new-item -ItemType $ItemType -path $p -Force -ea 1|out-null; return $true
    }Catch{
        If( test-path $log_file -ea 0 ){write-log Error $_.exception $log_file}
        return $false
    }Finally{}
  }
  return $true
}
Function Pop-Message(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$info,
        [Parameter(Mandatory=$False)]
        [string]$window_title='Script Running Error',
        [string]$info_type='Critical',
        [string]$log_file,
        [boolean]$noninteractive
    )
    # [void][System.Windows.Forms.MessageBox]::Show($info, $window_title, 'OK', $info_type)
    If( !$noninteractive ){[void][Microsoft.VisualBasic.Interaction]::MsgBox($info, "SystemModal,MsgBoxSetForeground,$info_type", $window_title)}
    elseif(test-path $log_file -ea 0){ write-log $info_type $info $log_file }
}
Function Stop-WithError(){ # this function is used to quit script when there is error with script running
    param(
		[Parameter(Mandatory=$True)]
    	[string]$log_file
    )
	Write-Log Error $error_detail $log_file
}
Function Check-FileExistence(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string[]]$file_list
    )
    Foreach( $f in $file_list){
        If( ! (test-path $f -ea 0) ){
            return $false
        }
    }
    return $true
}
Function New-Headers(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$False)]
        [string]$Charset='UTF-8',
        [string]$app_type='application/json',
        [string]$log_file
    )
    Try{
        $headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
        $headers.Add("Accept-Charset", $Charset)
        $headers.Add("Accept", $app_type)
        return $headers
    }Catch{
        write-log Warning 'There is an error when tring to create post header:' $log_file
        write-log Error $_ $log_file ; return $null
    }Finally{}
}
Function Get-ADObject(){
    [cmdletBinding()]
    param(
        [Parameter(Mandatory=$true)]
        [ValidatePattern("^(nam|apac|eur)\\.{7}")]
        [string]$fid,
        [Parameter(Mandatory=$False)]
        [string]$log_file="$home\Documents\WindowsPowerShell\log.txt"
    )
    $domain,$ad_name=$fid.split('\')
    $ad_searcher = New-Object DirectoryServices.DirectorySearcher([System.DirectoryServices.DirectoryEntry]"LDAP://dc=$domain,dc=nsroot,dc=net","(samaccountname=$ad_name)") #SearchRoot and Filter
    If(($ADObject = $ad_searcher.FindOne()) -eq $null){
        write-log Error "No such AD object $domain\$ad_name,please double check spelling!" $log_file
        return $null
    }else{
        return $ADObject.Properties
    }
}
Function Get-DSTInfo([Parameter(Mandatory=$true)][AllowNull()][string]$log_file){ # this fucntion is used to get this year winter time function date
    Process{
        Try{
            $TimeZone = Get-WmiObject -Class Win32_TimeZone
            $STNDHour = $TimeZone.StandardHour
            $STNDMonth = Switch ($TimeZone.StandardMonth){
                1  {"January"}
                2  {"February"}
                3  {"March"}
                4  {"April"}
                5  {"May"}
                6  {"June"}
                7  {"July"}
                8  {"August"}
                9  {"September"}
                10 {"October"}
                11 {"November"}
                12 {"December"}
            }
            [DateTime]$SDate = "$STNDMonth 01, $((Get-Date).Year) $STNDHour`:00:00"
            $i = 0
            While ($i -lt $TimeZone.StandardDay){
                If ($SDate.DayOfWeek -eq $TimeZone.StandardDayOfWeek){ $i++
                    If ($i -eq $TimeZone.StandardDay){
                        $SFinalDate = $SDate
                    }Else{
                        $SDate = $SDate.AddDays(1)
                    }
                }Else{
                    $SDate = $SDate.AddDays(1)
                }
            }
            If ($SFinalDate.Month -ne $TimeZone.StandardMonth){
               $SFinalDate = $SFinalDate.AddDays(-7)
            }
           return $SFinalDate
        }Catch{
            If($null -ne $log_file ){
                write-log Error $Error[0].Exception.Message $log_file
            }else{
                $_
            }
            return [datetime]::Now.AddDays(1)
        }
    }
}
Function Get-MSFromEpoch([Parameter(Mandatory=$False)][string]$log_file){ # this fucntion is used to get the total Milliseconds from 01/01/1970
    $count_hour=Switch ($this_domain){
        "nam"  {If([datetime]::Now -ge (Get-DSTInfo $log_file)){-5}else{-4} ; break }
        "apac" {8;break}
        "eur"  {0}
    }
    return '[math]::Truncate(([datetime]::Now - ([datetime]("1970 01-01")).AddHours(_hour_)).TotalMilliseconds)'.replace('_hour_',$count_hour)
}
Function Start-BulkThreads(){	# -Action { Stop-Process -Id $PID }
    param(
    	[Parameter(Mandatory=$True)]
    	[Object[]]$vdilist,             # the vdi list that scripts to be ran
    	[Parameter(Mandatory=$True)]    
    	[ScriptBlock]$Action,           # the actual action that each thread will run
    	[Parameter(Mandatory=$True)]
		[AllowNull()]		
    	[Object[]]$ActionParams,        # the parameters that each thread needs, this can be a single or array of parameter
    	[Parameter(Mandatory=$false)]   
    	[int]$maxCnt=100                # the default amount of powershell threads, if this number is big, then the system might get stuck
    )
	$results=@() # this is used to collect the result from each thread
	$vdiCounts = $vdilist.Count
	If( $vdiCounts -eq 1 ){ # no need to create thread pool to run it
		If( [string]::IsNullOrWhiteSpace($vdilist[0]) ){
		    write-error "vdilist only contains 1 line but it's null"
		}else{& $Action $vdilist[0] 1 $ActionParams}
	}else{
		$vdiCounts = $vdilist.Count ; $throttleLimit=$maxCnt ; If($vdiCounts -le $maxCnt ){$throttleLimit=$vdiCounts}
		# creaate the thread pool and run each thread based the vdi list amount
		$SessionState = [system.management.automation.runspaces.initialsessionstate]::CreateDefault()
		$Pool = [runspacefactory]::CreateRunspacePool(1, $throttleLimit, $SessionState, $Host)
		$Pool.Open() ; $threads = @() ; $cnt=1
		$handles = foreach($line in $vdilist){
			if( [string]::IsNullOrWhiteSpace($line ) ){continue}
			$powershell = [powershell]::Create().AddScript($Action).AddArgument($line).AddArgument($cnt).AddArgument($ActionParams)
			$powershell.RunspacePool = $Pool ;	$powershell.BeginInvoke() ; $threads += $powershell ; $cnt++
		}
		# wait each thread to be done and then collect its return value
		do{
		  $i = 0 ;  $done = $true
		  foreach ($handle in $handles) {
			if($handle -ne $null){if($handle.IsCompleted){$results+=$threads[$i].EndInvoke($handle);$threads[$i].Dispose();$handles[$i]=$null}else{$done = $false}}
			$i++
		  }
		  if (-not $done) { Start-Sleep -Milliseconds 200 }
		} until ($done)
	}
	return $results # return the value collection from each thread and then export to csv or take other further actions
}
Function Connect-Sql(){
    param(
        [Parameter(Mandatory=$True)]
        [string]$DBServer,
        [string]$DBPwd,
        [Parameter(Mandatory=$False)]
        [string]$log_file='dbquery.log'
    )
    $connection = New-Object System.Data.SqlClient.SqlConnection
    $connection.ConnectionString = "Server=$DBServer;uid=aa_dbread;pwd=$DBPwd;Database=ICGDB;Integrated Security=False;"
    Try{
       $connection.Open() ; return $connection
    }Catch{
       write-log Error $_.exception.Message $log_file
       Write-Log Error "Failed to connect to DB server $DBServer with user 'aa_dbread'!" $log_file ;  return $null
    }Finally{}
}
Function Execute-Sql(){
    param(
        [Parameter(Mandatory=$True)]
        $connection,
        [Parameter(Mandatory=$False)]
        [string]$query,
        [string]$log_file='dbquery.log'
    )
    Try{
        $adapter = New-Object System.Data.sqlclient.sqlDataAdapter (new-object system.data.sqlclient.sqlcommand($query,$connection))
        $dataset = New-Object System.Data.DataSet ; $adapter.Fill($dataSet) | Out-Null
        return $dataSet.Tables[0]
    }Catch{
        Write-Log Error $_.exception.Message $log_file ; return $null
    }Finally{}
}
Function Get-FIDFromDB(){
    [CmdletBinding()]param(
        [Parameter(Mandatory=$True)]
        [string]$db_connetion,
        [string]$fid_query,
        [string]$log_file
    )
    If(($db_fid_detail=Execute-Sql $db_connection $fid_query $log_file) -eq $null){
        write-log Error "$cnt FID: $fid<--DB sql query failed-->$db_server" $log_file ; $db_connection.close() ; return $null
    }else{
        write-log Success "Found FID $fid ID $($db_fid_detail.ID) in $db_domain $db_env DB" $log_file
        return $db_fid_detail
    }
}
Try{
    $this_path = Split-Path -Parent ($MyInvocation.MyCommand.Definition) -ea 1
}Catch{
    $this_path = [System.IO.Path]::GetDirectoryName([System.Diagnostics.Process]::GetCurrentProcess().MainModule.FileName)
}Finally{}
$this_domain=(Get-WmiObject Win32_ComputerSystem).Domain.Split(".")[0]
[void][System.Reflection.Assembly]::LoadWithPartialName("Microsoft.VisualBasic") 
[void][System.Reflection.Assembly]::Load([io.file]::ReadAllBytes("$this_path\CredentialValidate.dll"))


